﻿#region Usings

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using SilverBox.Services;

#endregion

namespace SilverBox.Helpers
{
    public static class ControlTreeHelper
    {
        private static readonly VisualTreeCache VisualCache;

        static ControlTreeHelper()
        {
            VisualCache = VisualTreeCache.Current;
        }

        /// <summary>
        /// Devuelve el contenedor de un UserControl
        /// </summary>
        /// <param name="element">elemento del cual queremeos conocer el padre</param>
        /// <returns></returns>
        public static FrameworkElement FindParentUserControl(FrameworkElement element)
        {
            FrameworkElement result = null;
            if (element != null)
            {
                while (element.Parent != null && !(element.Parent is UserControl))
                {
                    if (element.Parent is FrameworkElement)
                    {
                        element = (FrameworkElement)element.Parent;
                    }
                    else
                    {
                        break;
                    }
                }
                if (element is UserControl)
                {
                    result = element;
                }
            }
            return result;
        }

        public static T FindUniqueParentControl<T>(DependencyObject leaf)
        {
            var res = default(T);
            if (leaf != null)
            {
                var tmp = VisualTreeHelper.GetParent(leaf);
                while (tmp != null && tmp.GetType() != typeof(T))
                {
                    tmp = VisualTreeHelper.GetParent(tmp);
                }
                if (tmp != null)
                {
                    if (tmp.GetType() == typeof(T))
                    {
                        res = (T)((object)tmp);
                    }
                }
            }
            return res;
        }

        public static List<DependencyObject> GetAllParentControls(DependencyObject element)
        {
            var list = new List<DependencyObject> { element };

            while (VisualTreeHelper.GetParent(element) != null)
            {
                list.Add(VisualTreeHelper.GetParent(element));

                element = VisualTreeHelper.GetParent(element);
            }
            return list;
        }

        public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
        {
            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                var child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is T)
                    return (T)child;
                var childOfChild = FindVisualChild<T>(child);
                if (childOfChild != null)
                    return childOfChild;
            }
            return null;
        }

        /// <summary>
        /// Devuelve la lista de controles de usuario de una ventana
        /// </summary>
        /// <param name="window">Ventana de la cual queremos conocer los controles de usuario</param>
        /// <returns></returns>
        public static List<UserControl> GetAllUserControl(FrameworkElement window)
        {
            return GetAllControlsByType<UserControl>(window);
        }

        /// <summary>
        /// Busca el control único desde esa posición del arbol
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="root"></param>
        /// <returns></returns>
        public static T GetUniqueControl<T>(DependencyObject root)
        {
            T res = default(T);
            var tmp = GetAllControlsByType<T>(root);
            if (tmp.Count > 0)
            {
                res = tmp[0];
            }
            return res;
        }

        /// <summary>
        /// Obtiene una lista con todos los controles a partir del Type especificado
        /// </summary>
        /// <typeparam name="T">Filtro de tipo</typeparam>
        /// <param name="root"></param>
        /// <returns></returns>
        public static List<T> GetAllControlsByType<T>(DependencyObject root)
        {
            var list = new List<T>();
            var tmp = new List<DependencyObject>();
            ExploreTree(tmp, root);
            for (var x = 0; x < tmp.Count; x++)
            {
                if (tmp[x].GetType() == typeof(T) || tmp[x].GetType().IsSubclassOf(typeof(T)))
                {
                    list.Add((T)((object)tmp[x]));
                }
            }
            return list;
        }

        private static void ExploreTree(ICollection<DependencyObject> list, DependencyObject obj)
        {
            if (list == null || obj == null) return;
            var childrens = VisualTreeHelper.GetChildrenCount(obj);
            for (var x = 0; x < childrens; x++)
            {
                var tmp = VisualTreeHelper.GetChild(obj, x);
                list.Add(tmp);
                ExploreTree(list, tmp);
            }
        }

        /// <summary>
        /// Lista de todos los controles de un control de usuario
        /// </summary>
        /// <param name="userControl">Control de usuario del cual queremos conocer los controles</param>
        /// <returns></returns>
        public static List<FrameworkElement> GetAllControls(UserControl userControl)
        {
            var list = new List<FrameworkElement>();
            InternalGetAllControls(list, userControl);
            if (list.Contains(userControl))
            {
                list.Remove(userControl);
            }
            return list;
        }

        /// <summary>
        /// Explora el arbol visual del control y devulve una lista con todos los controles encontrados
        /// </summary>
        /// <param name="obj">Raiz a partir de la cual se generará la lista de todos los controles del arbol visual</param>
        /// <returns></returns>
        public static List<DependencyObject> GetVisualTree(DependencyObject obj)
        {
            var tmp = new List<DependencyObject>();
            ExploreTree(tmp, obj);
            return tmp;
        }

        [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        private static void InternalGetAllControls(ICollection<FrameworkElement> list, FrameworkElement userControl)
        {
            if (list == null || userControl == null) return;
            list.Add(userControl);

            if (userControl is ContentControl)
            {
                var actual = (ContentControl)userControl;
                if (actual.Content is FrameworkElement)
                {
                    list.Add((FrameworkElement)actual.Content);
                }
                if (actual.Content is Panel)
                {
                    userControl = (FrameworkElement)actual.Content;
                }
            }

            if (!(userControl is Panel)) return;
            var panel = (Panel)userControl;
            for (var x = 0; x < panel.Children.Count; x++)
            {
                if (panel.Children[x] is FrameworkElement)
                {
                    list.Add((FrameworkElement)panel.Children[x]);
                }
                else
                {
                    InternalGetAllControls(list, (FrameworkElement)panel.Children[x]);
                }
            }
        }

        public static T FindNameInVisualTree<T>(DependencyObject root, string name)
        {
            return FindNameInVisualTree<T>(root, name, false);
        }

        public static T FindNameInVisualTree<T>(DependencyObject root, string name, bool cache)
        {
            var res = default(T);
            var tree = cache ? VisualCache[root] : GetVisualTree(root);
            for (var x = 0; x < tree.Count; x++)
            {
                if (!(tree[x] is FrameworkElement)) continue;
                if (((FrameworkElement) tree[x]).Name != name) continue;
                res = (T) (object) tree[x];
                break;
            }
            return res;
        }
    }
}